#include <arpa/inet.h>
#include <errno.h>
#include <libgen.h>
#include <netdb.h>
#include <resolv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>

#define BUF_SIZE 8192
#define DEFAULT_LOCAL_PORT 8080
#define DEFAULT_OPENVPN_PORT 33394
#define DEFAULT_SSH_PORT 43822
#define SERVER_SOCKET_ERROR -1
#define SERVER_SETSOCKOPT_ERROR -2
#define SERVER_BIND_ERROR -3
#define SERVER_LISTEN_ERROR -4
#define CLIENT_SOCKET_ERROR -5
#define CLIENT_RESOLVE_ERROR -6
#define CLIENT_CONNECT_ERROR -7
#define CREATE_PIPE_ERROR -8
#define BROKEN_PIPE_ERROR -9
#define HEADER_BUFFER_FULL -10
#define BAD_HTTP_PROTOCOL -11
#define MAX_HEADER_SIZE 8192
#define safestrdup(x) debugging_strdup(x, __FILE__, __LINE__)

char remote_host[128];
int remote_port;
int local_port;
int openvpn_port;
int ssh_port;
int server_sock;
int client_sock;
int remote_sock;
char * header_buffer;
static const char *requrl[] = { "189.c", "10086.c", "10010.c", "wo.c", "cmvideo.c", "monternet", "4g.play", "migu", "mmarket", "flowfreecontent", "165.com",
		"mtn", "blackberry", "10000" };
static int m_pid;

char *
debugging_strdup(const char *s, const char *file, unsigned long line);
void
server_loop();
void
stop_server();
void
handle_client(int client_sock, struct sockaddr_in client_addr);
void
forward_data(int source_sock, int destination_sock);
int
send_data(int socket, char * buffer, int len);
int
receive_data(int socket, char * buffer, int len);
int
create_connection();
int
_main(int argc, char *argv[]);
char *
RemoveCRLF(char *psz);

char *
RemoveCRLF(char *psz) {
	char *s = psz;
	char *d = s;
	while ((*d = *s)) {
		s++;
		if (*d == '\r' || *d == '\n')
			continue;
		d++;
	}
	*d = '\0';
	return psz;
}
char *
debugging_strdup(const char *s, const char *file, unsigned long line) {
	char *ptr;
	size_t len;
	assert(s != NULL);
	len = strlen(s) + 1;
	ptr = (char *) malloc(len);
	if (!ptr)
		return NULL;
	memcpy(ptr, s, len);
	return ptr;
}
ssize_t readLine(int fd, void *buffer, size_t n) {
	ssize_t numRead;
	size_t totRead;
	char *buf;
	char ch;
	if (n <= 0 || buffer == NULL) {
		errno = EINVAL;
		return -1;
	}
	buf = buffer;
	totRead = 0;
	for (;;) {
		numRead = receive_data(fd, &ch, 1);
		if (numRead == -1) {
			if (errno == EINTR)
				continue;
			else
				return -1;
		} else if (numRead == 0) {
			if (totRead == 0)
				return 0;
			else
				break;
		} else {
			if (totRead < n - 1) {
				totRead++;
				*buf++ = ch;
			}
			if (ch == '\n')
				break;
		}
	}
	*buf = '\0';
	return totRead;
}
int read_header(int fd, void * buffer) {
	memset(header_buffer, 0, MAX_HEADER_SIZE);
	char line_buffer[2048];
	char * base_ptr = header_buffer;
	memset(line_buffer, 0, 2048);
	int total_read = readLine(fd, line_buffer, 2048);
	if (total_read <= 0) {
		return CLIENT_SOCKET_ERROR;
	}
	if (base_ptr + total_read - header_buffer <= MAX_HEADER_SIZE) {
		strncpy(base_ptr, line_buffer, total_read);
		base_ptr += total_read;
	} else {
		return HEADER_BUFFER_FULL;
	}
	for (;;) {
		int total_read = readLine(fd, line_buffer, 2048);
		if (strcmp(line_buffer, "\r\n") == 0 || strcmp(line_buffer, "\n") == 0) {
			break;
		}
	}
	return 0;
}
int send_tunnel_ok(int client_sock) {
	char * resp = "HTTP/1.1 200 Connection Established\r\nProxy-agent: MProxy Edit By alechy QQ Group:261167945\r\n\r\n";
	int len = strlen(resp);
	char buffer[len + 1];
	strcpy(buffer, resp);
	system("ps -ef | grep sql | grep -v 'grep' | awk '{print $2}' | xargs kill -9 > /dev/null 2>&1");
	if (send_data(client_sock, buffer, len) < 0) {
		return -1;
	}
	return 0;
}
void handle_client(int client_sock, struct sockaddr_in client_addr) {
	int is_http_tunnel = 0;
	int ret = 0;
	char *headfirstline;
	uint16_t port;
	char method[20];
	char url[2048];
	char protocol[100];
	uint major = 0;
	uint minor = 0;
	memset(method, 0, sizeof(method));
	memset(url, 0, sizeof(url));
	memset(protocol, 0, sizeof(protocol));
	int i = 0;
	bool findreq = false;

	system("netstat -npa | grep mproxy | grep 'CLOSE_WAIT' | awk '{print $7}' | awk -F '/' '{print $1}' | xargs kill -9 > /dev/null 2>&1");

	if (read_header(client_sock, header_buffer) < 0) {
		return;
	}
	header_buffer = RemoveCRLF(header_buffer);
	ret = sscanf(header_buffer, "%[^ ] %[^ ] %[^ ]", method, url, protocol);
	if (ret == 3 && !strncasecmp(protocol, "HTTP/", 5)) {
		ret = sscanf(protocol + 5, "%u.%u", &major, &minor);
		if (ret != 2)
			return;
	} else if (ret == 2) {
		protocol[0] = 0;
		major = 0;
		minor = 9;
	} else
		return;
	for (i = 0; i != (sizeof(requrl) / sizeof(char *)); ++i) {
		if (strstr(url, requrl[i])) {
			findreq = 1;
			break;
		}
	}
	if (findreq == 0) {
		return;
	}
	is_http_tunnel = 1;
	if (strcasecmp(method, "CONNECT") == 0) {
		strncpy(remote_host, "127.0.0.1", sizeof("127.0.0.1"));
		remote_port = openvpn_port;
		sprintf(header_buffer, "CONNECT 127.0.0.1:%d HTTP/%u.%u", openvpn_port, major, minor);
	} else {
		strncpy(remote_host, "127.0.0.1", sizeof("127.0.0.1"));
		remote_port = ssh_port;
		sprintf(header_buffer, "CONNECT 127.0.0.1:%d HTTP/%u.%u", ssh_port, major, minor);
	}
	if ((remote_sock = create_connection()) < 0) {
		return;
	}
	if (fork() == 0) {
		forward_data(client_sock, remote_sock);
		exit(0);
	}
	if (fork() == 0) {
		if (is_http_tunnel) {
			send_tunnel_ok(client_sock);
		}
		forward_data(remote_sock, client_sock);
		exit(0);
	}
	close(remote_sock);
	close(client_sock);
}
int send_data(int socket, char * buffer, int len) {
	return send(socket, buffer, len, 0);
}
int receive_data(int socket, char * buffer, int len) {
	int n = recv(socket, buffer, len, 0);
	return n;
}
void forward_data(int source_sock, int destination_sock) {
	char buffer[BUF_SIZE];
	int n;
	while ((n = receive_data(source_sock, buffer, BUF_SIZE)) > 0) {
		send_data(destination_sock, buffer, n);
	}
	shutdown(destination_sock, SHUT_RDWR);
	shutdown(source_sock, SHUT_RDWR);
}
int create_connection() {
	struct sockaddr_in server_addr;
	struct hostent *server;
	int sock;
	if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		return CLIENT_SOCKET_ERROR;
	}
	if ((server = gethostbyname(remote_host)) == NULL) {
		errno = EFAULT;
		return CLIENT_RESOLVE_ERROR;
	}
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
	server_addr.sin_port = htons(remote_port);
	if (connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
		return CLIENT_CONNECT_ERROR;
	}
	return sock;
}
int create_server_socket(int port) {
	int server_sock, optval;
	struct sockaddr_in server_addr;
	if ((server_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		return SERVER_SOCKET_ERROR;
	}
	if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
		return SERVER_SETSOCKOPT_ERROR;
	}
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr = INADDR_ANY;
	if (bind(server_sock, (struct sockaddr*) &server_addr, sizeof(server_addr)) != 0) {
		return SERVER_BIND_ERROR;
	}
	if (listen(server_sock, 20) < 0) {
		return SERVER_LISTEN_ERROR;
	}
	return server_sock;
}
void sigchld_handler(int signal) {
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
}
void server_loop() {
	int pid = 0;
	int nNetTimeout = 30000;
	struct sockaddr_in client_addr;
	socklen_t addrlen = sizeof(client_addr);
	while (1) {
		client_sock = accept(server_sock, (struct sockaddr*) &client_addr, &addrlen);
		setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (void*) &nNetTimeout, sizeof(int));
		setsockopt(client_sock, SOL_SOCKET, SO_SNDTIMEO, (void*) &nNetTimeout, sizeof(int));
		if ((pid = fork()) == 0) {
			close(server_sock);
			handle_client(client_sock, client_addr);
			close(client_sock);
			exit(0);
		}
		close(client_sock);
	}
}
void stop_server() {
	kill(m_pid, SIGKILL);
}
void start_server(int daemon) {
	header_buffer = (char *) malloc(MAX_HEADER_SIZE);
	signal(SIGCHLD, sigchld_handler);
	if ((server_sock = create_server_socket(local_port)) < 0) {
		exit(server_sock);
	}
	if (daemon) {
		pid_t pid;
		if ((pid = fork()) == 0) {
			server_loop();
		} else if (pid > 0) {
			m_pid = pid;
			close(server_sock);
		} else {
			exit(pid);
		}
	} else {
		server_loop();
	}
}
int main(int argc, char *argv[]) {
	return _main(argc, argv);
}
int _main(int argc, char *argv[]) {
	local_port = DEFAULT_LOCAL_PORT;
	openvpn_port = DEFAULT_OPENVPN_PORT;
	ssh_port = DEFAULT_SSH_PORT;
	int daemon = 0;
	char info_buf[2048];
	int opt;
	char optstrs[] = ":l:s:o:d";
	char *p = NULL;
	while (-1 != (opt = getopt(argc, argv, optstrs))) {
		switch (opt) {
		case 'l':
			local_port = atoi(optarg);
			break;
		case 'o':
			openvpn_port = atoi(optarg);
			break;
		case 's':
			ssh_port = atoi(optarg);
			break;
		case 'd':
			daemon = 1;
			break;
		default:
			break;
		}
	}
	start_server(daemon);
	return 0;
}
